package pers.vic.boot.security.shiro;

import org.apache.shiro.mgt.DefaultSessionStorageEvaluator;
import org.apache.shiro.mgt.DefaultSubjectDAO;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.context.annotation.Import;
import org.springframework.data.redis.core.RedisTemplate;
import pers.vic.boot.security.autoconfigure.SecurityProperties;
import pers.vic.boot.security.jwt.JwtService;
import pers.vic.boot.security.redis.RedisService;
import pers.vic.boot.security.shiro.extend.CustomerShiroFilterFactoryBean;

import javax.servlet.Filter;
import java.util.*;

/**
 * @description: SHIRO 配置类， 依赖于REDIS
 * @author: Vic.xu
 * @date: 2019年12月31日 下午1:11:41
 */
@Configuration
@ConditionalOnClass(Filter.class)
@ConditionalOnBean(RedisTemplate.class)
@ConditionalOnExpression("${auth.shiro.enabled:true}")
@Import({ShiroLifecycleBeanPostProcessorConfig.class})
@AutoConfigureAfter(ShiroLifecycleBeanPostProcessorConfig.class)
public class ShiroConfig {
    /*
     * Autowired无法正常注入的疑难杂症 https://www.lagou.com/lgeduarticle/66756.html\
     */

    @Autowired
    private SecurityProperties securityProperties;

    /**
     * jwtService
     */
    @Bean
    @ConditionalOnBean(RedisTemplate.class)
    @ConditionalOnMissingBean
    public JwtService jwtService(RedisService redisService) {
        return new JwtService(redisService);
    }

    /**
     * 注册jwtFilter
     *
     * @return
     */
    @Bean
    public JwtFilter jwtFilter() {
        return new JwtFilter();
    }

    @Bean
    @ConditionalOnMissingBean
    public MyRealm myRealm(JwtService jwtService) {
        return new MyRealm(jwtService);
    }

    /**
     * 删除URL中的sessionId的Filter
     *
     * @return
     */
    @Bean("registerRemoveUrlSessionFilter")
    public FilterRegistrationBean<RemoveUrlSessionFilter> registFilter() {
        FilterRegistrationBean<RemoveUrlSessionFilter> registration = new FilterRegistrationBean<>();
        registration.setFilter(new RemoveUrlSessionFilter());
        registration.addUrlPatterns("/*");
        registration.setName("removeUrlSessionFilter");
        registration.setOrder(-1);
        return registration;
    }

    /**
     * 注册jwtFilter到spring
     *
     * @param jwtFilter
     * @return
     */
    @Bean("registerJwtFilter")
    @ConditionalOnBean(JwtFilter.class)
    public FilterRegistrationBean<JwtFilter> registerJwtFilter(@Autowired JwtFilter jwtFilter) {
        // 设置jwt filter不自动注册到spring管理的监听器中，防止与shiro filter同级，导致该监听器必定执行
        FilterRegistrationBean<JwtFilter> jwtFilterRegister = new FilterRegistrationBean<>(jwtFilter);
        jwtFilterRegister.setEnabled(false);
        return jwtFilterRegister;
    }

    @Bean("securityManager")
    @ConditionalOnMissingBean
    public DefaultWebSecurityManager getManager(MyRealm realm) {
        DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
        /*
         * 关闭shiro自带的session，详情见文档
         * http://shiro.apache.org/session-management.html#SessionManagement-
         * StatelessApplications%28Sessionless%29
         */
        DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
        DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();
        defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
        subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
        manager.setSubjectDAO(subjectDAO);
        // 使用自己的realm
        manager.setRealm(realm);
        return manager;
    }

    /**
     * 使用CustomerShiroFilterFactoryBean 代替 ShiroFilterFactoryBean 加入忽略更新最后请求时间的逻辑
     *
     * @param securityManager
     * @return
     */
    @Bean("shiroFilter")
    @ConditionalOnMissingBean
    public ShiroFilterFactoryBean shiroFilter(DefaultWebSecurityManager securityManager) {
        CustomerShiroFilterFactoryBean factoryBean = new CustomerShiroFilterFactoryBean();

        // 加入忽略更新最后请求时间的url
        Set<String> ignoreUpdateLastAccessTimeUrl = new HashSet<String>();
        ignoreUpdateLastAccessTimeUrl.add("/polling/test1");
        factoryBean.setIgnoreUpdateLastAccessTimeUrl(ignoreUpdateLastAccessTimeUrl);

        // 添加自己的过滤器并且取名为jwt
        Map<String, Filter> filterMap = new HashMap<>(16);
        /**
         * 修改new JwtFilter() 为jwtFilter(), JwtFilter交由spring管理,方便在JwtFilter 中注入
         * 但是JwtFilter交给Spring管理后，Spring将其注册到filterChain中了，与ShiroFilter同级，
         * 所以即使设置了filter的order，在shiroFilter完了之后也会经过JwtFilter，从而导致认证请求调用链的异常
         * 依然把JwtFilter交由Spring管理，但是设置这个bean不要注册到filter调用链中: 通过FilterRegistrationBean取消JwtFilter的自动注册， 参考 文档
         * https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#howto-disable-registration-of-a-servlet-or-filter
         *
         */
        filterMap.put("jwt", jwtFilter());
        factoryBean.setFilters(filterMap);
        factoryBean.setSecurityManager(securityManager);

        /*
         * 自定义url规则 http://shiro.apache.org/web.html#urls-
         */
        LinkedHashMap<String, String> filterRuleMap = new LinkedHashMap<>();

        // 访问401和404页面不通过我们的Filter
        // 可匿名访问
        filterRuleMap.put("/login", "anon");
        filterRuleMap.put("/test/**", "anon");
        // 验证码
        filterRuleMap.put("/captcha", "anon");
        // 一些静态资源
        filterRuleMap.put("/**/*.js", "anon");
        filterRuleMap.put("/**/*.css", "anon");
        filterRuleMap.put("/**/*.ico", "anon");
        filterRuleMap.put("/img/**", "anon");
        filterRuleMap.put("/assets/**", "anon");
        filterRuleMap.put("/imgages/**", "anon");
        filterRuleMap.put("/lib/**", "anon");
        // 访问附件attachment
        filterRuleMap.put("/attachment/visit/**", "anon");
        // 加入自定义配置的拦截规则
        Map<String, String> customerRule = securityProperties.getFilteRuleMap();
        filterRuleMap.putAll(customerRule);
        // 所有请求通过我们自己的 也可put "jwt,authc"\
        // 加了authc不用每个requestMapping都加@RequiresAuthentication
        filterRuleMap.put("/**", "jwt,authc");
        // 如果重写了/** 则最后加进去
        if (customerRule.containsKey("/**")) {
            filterRuleMap.put("/**", customerRule.get("/**"));
        }
        factoryBean.setFilterChainDefinitionMap(filterRuleMap);
        return factoryBean;
    }

    /**
     * 下面的代码是添加注解支持
     */
    // 扫描上下文，寻找所有的Advistor(一个Advisor是一个切入点和一个通知的组成)，将这些Advisor应用到所有符合切入点的Bean中
    @Bean
    @ConditionalOnMissingBean
    @DependsOn("lifecycleBeanPostProcessor")
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
        // 强制使用cglib，防止重复代理和可能引起代理出错的问题
        // https://zhuanlan.zhihu.com/p/29161098
        defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
        return defaultAdvisorAutoProxyCreator;
    }

    // 开启shiro aop注解支持
    @Bean
    @ConditionalOnMissingBean
    public AuthorizationAttributeSourceAdvisor
    authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
        advisor.setSecurityManager(securityManager);
        return advisor;
    }
}
